<?php

namespace Hilaoyu\ComputeCloudClient\Api;


use Hilaoyu\ComputeCloudClient\AssetModel;
use Hilaoyu\ComputeCloudClient\AssetModels;
use Hilaoyu\ComputeCloudClient\huawei\DetachServerInterfaceResponse;
use Hilaoyu\ComputeCloudClient\huawei\ShowServerInterfaceResponse;
use Hilaoyu\ComputeCloudClient\Interfaces\ApiEcsInterface;
use Hilaoyu\Utils\UtilArr;
use Hilaoyu\Utils\UtilFile;
use Hilaoyu\Utils\UtilHttp;
use Hilaoyu\Utils\UtilStr;
use Hilaoyu\Utils\UtilSupport;
use HuaweiCloud\SDK\Core\Exceptions\ServiceResponseException;
use HuaweiCloud\SDK\Ecs\V2\EcsClient;
use HuaweiCloud\SDK\Ecs\V2\Model\AttachServerVolumeOption;
use HuaweiCloud\SDK\Ecs\V2\Model\AttachServerVolumeRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\AttachServerVolumeRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchRebootServersRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchRebootServersRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchRebootSeversOption;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchStartServersOption;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchStartServersRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchStartServersRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchStopServersOption;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchStopServersRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\BatchStopServersRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\ChangeServerOsWithoutCloudInitOption;
use HuaweiCloud\SDK\Ecs\V2\Model\ChangeServerOsWithoutCloudInitRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\ChangeServerOsWithoutCloudInitRequestBody;
use Hilaoyu\ComputeCloudClient\huawei\ChangeVpcResponse;
use HuaweiCloud\SDK\Ecs\V2\Model\CreatePostPaidServersRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\CreatePostPaidServersRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\CreateServersRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\CreateServersRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\DeleteServersRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\DeleteServersRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\DetachServerVolumeRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\GetServerRemoteConsoleOption;
use HuaweiCloud\SDK\Ecs\V2\Model\ListFlavorsRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\ListServerBlockDevicesRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\ListServersDetailsRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServer;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServerEip;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServerEipBandwidth;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServerExtendParam;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServerNic;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServerPublicip;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServerRootVolume;
use HuaweiCloud\SDK\Ecs\V2\Model\PostPaidServerSecurityGroup;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServer;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServerEip;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServerEipBandwidth;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServerExtendParam;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServerNic;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServerPublicip;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServerRootVolume;
use HuaweiCloud\SDK\Ecs\V2\Model\PrePaidServerSecurityGroup;
use HuaweiCloud\SDK\Ecs\V2\Model\ReinstallServerWithoutCloudInitOption;
use HuaweiCloud\SDK\Ecs\V2\Model\ReinstallServerWithoutCloudInitRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\ReinstallServerWithoutCloudInitRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\ResetServerPasswordOption;
use HuaweiCloud\SDK\Ecs\V2\Model\ResetServerPasswordRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\ResetServerPasswordRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\ServerBlockDevice;
use HuaweiCloud\SDK\Ecs\V2\Model\ServerDetail;
use HuaweiCloud\SDK\Ecs\V2\Model\ServerId;
use HuaweiCloud\SDK\Ecs\V2\Model\ShowServerRemoteConsoleRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\ShowServerRemoteConsoleRequestBody;
use HuaweiCloud\SDK\Ecs\V2\Model\ShowServerRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\UpdateServerOption;
use HuaweiCloud\SDK\Ecs\V2\Model\UpdateServerRequest;
use HuaweiCloud\SDK\Ecs\V2\Model\UpdateServerRequestBody;
use HuaweiCloud\SDK\Evs\V2\Model\CreateSnapshotOption;
use HuaweiCloud\SDK\Evs\V2\Model\CreateSnapshotRequest;
use HuaweiCloud\SDK\Evs\V2\Model\CreateSnapshotRequestBody;
use HuaweiCloud\SDK\Evs\V2\Model\CreateVolumeOption;
use HuaweiCloud\SDK\Evs\V2\Model\CreateVolumeRequest;
use HuaweiCloud\SDK\Evs\V2\Model\CreateVolumeRequestBody;
use HuaweiCloud\SDK\Evs\V2\Model\DeleteVolumeRequest;
use HuaweiCloud\SDK\Evs\V2\Model\ListVolumesRequest;
use HuaweiCloud\SDK\Evs\V2\Model\UpdateVolumeOption;
use HuaweiCloud\SDK\Evs\V2\Model\UpdateVolumeRequest;
use HuaweiCloud\SDK\Evs\V2\Model\UpdateVolumeRequestBody;
use HuaweiCloud\SDK\Ims\V2\Model\ListImagesRequest;
use HuaweiCloud\SDK\Vpc\V2\Model\CreatePortOption;
use HuaweiCloud\SDK\Vpc\V2\Model\CreatePortRequest;
use HuaweiCloud\SDK\Vpc\V2\Model\CreatePortRequestBody;
use HuaweiCloud\SDK\Vpc\V2\Model\DeletePortRequest;
use HuaweiCloud\SDK\Vpc\V2\Model\ListPortsRequest;
use HuaweiCloud\SDK\Vpc\V2\Model\ShowPortRequest;
use HuaweiCloud\SDK\Vpc\V3\Model\CreateSecurityGroupRuleOption;
use HuaweiCloud\SDK\Vpc\V3\Model\CreateSecurityGroupRuleRequest;
use HuaweiCloud\SDK\Vpc\V3\Model\CreateSecurityGroupRuleRequestBody;
use HuaweiCloud\SDK\Vpc\V3\Model\DeleteSecurityGroupRequest;
use HuaweiCloud\SDK\Vpc\V3\Model\DeleteSecurityGroupRuleRequest;
use HuaweiCloud\SDK\Vpc\V3\Model\ListSecurityGroupRulesRequest;
use HuaweiCloud\SDK\Vpc\V3\Model\ListSecurityGroupsRequest;
use HuaweiCloud\SDK\Vpc\V3\Model\CreateSecurityGroupOption;
use HuaweiCloud\SDK\Vpc\V3\Model\CreateSecurityGroupRequest;
use HuaweiCloud\SDK\Vpc\V3\Model\CreateSecurityGroupRequestBody;

/**
 * Class HuaWeiEcs
 * @package Hilaoyu\ComputeCloudClient\Api
 * @property  \HuaweiCloud\SDK\Ecs\V2\EcsClient $client
 */
class HuaWeiEcs extends HuaWeiApiBase implements ApiEcsInterface
{

    //安全组
    public function securityGroupCreate(string $securityGroupName = null, string $vpcId = null, string $securityGroupType = 'normal', $regionId = '', $resourceGroupId = null): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);
        try {
            $request = (new CreateSecurityGroupRequest())
                ->setBody(
                    (new CreateSecurityGroupRequestBody())
                        ->setDryRun(false)
                        ->setSecurityGroup(
                            (new CreateSecurityGroupOption())
                                ->setName($securityGroupName)
                                ->setDescription($vpcId)
                        )
                );
            $result = $this->clientVpcV3()->createSecurityGroup($request);

            $data = $result->getSecurityGroup();

            $id = $data->getId();
            if (!$id) {
                throw new \Exception('返回数据错误');
            }


            $model = new AssetModel();
            $model->type = 'sg';
            $model->asset_id = $id;
            $model->name = (string)$data->getName();
            $model->os_type = (string)$securityGroupType;
            $model->region_id = (string)$regionId;
            $model->bind_asset_id = (string)$vpcId;
            return $model;


        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }

    public function securityGroupDelete(string $securityGroupId, $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        $securityGroupId = trim($securityGroupId);
        if (!$securityGroupId) {
            return false;
        }
        if (!$this->securityGroupDescribe($securityGroupId)) {
            return true;
        }
        try {
            $request = (new DeleteSecurityGroupRequest())
                ->setSecurityGroupId($securityGroupId);
            $result = $this->clientVpcV3()->deleteSecurityGroup($request);

            return (bool)$result->valid();
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function securityGroupsDescribe(array $securityGroupIds = [], string $securityGroupName = null, string $vpcId = null, int $pageSize = 100, int $pageNumber = 1, $regionId = '', $resourceGroupId = null): AssetModels
    {
        $regionId = $this->getRegionId($regionId);
        $models = new AssetModels();
        try {
            $request = new ListSecurityGroupsRequest();
            if (!empty($securityGroupIds)) {
                $request->setId($securityGroupIds);
            }

            if ($securityGroupName) {
                $request->setName((array)$securityGroupName);
            }
            if ($vpcId) {
                $request->setDescription((array)$vpcId);
            }

            $result = $this->clientVpcV3()->listSecurityGroups($request);
            $data = (array)$result->getSecurityGroups();

            foreach ($data as $datum) {
                $id = $datum->getId();
                if (!$id) {
                    continue;
                }

                $model = new AssetModel();
                $model->type = 'sg';
                $model->asset_id = $id;
                $model->name = $datum->getName();
                $model->os_type = 'normal';
                $model->bind_asset_id = $datum->getDescription();
                $model->region_id = (string)$regionId;

                $models->push($model);
            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function securityGroupDescribe(string $securityGroupId, $regionId = ''): ?AssetModel
    {
        $models = $this->securityGroupsDescribe([$securityGroupId], null, null, 10, 1, $regionId);
        if (!$models) {
            return null;
        }


        return $models->first(function ($item) use ($securityGroupId) {
            return $item->asset_id == $securityGroupId;
        });
        return null;
    }


    protected function securityGroupAuthorizeInOrOut(string $inOrOut, $securityGroupId, $portRange, $policy = 'accept', $sourceCidrIp = '0.0.0.0/0', $ipProtocol = 'tcp', $sourcePortRange = null, $destCidrIp = null, $priority = 1, $regionId = ''): bool
    {
        //先删除一次，不然会报错
        $this->securityGroupRevokeInOrOut($inOrOut, $securityGroupId, $portRange, $policy, $sourceCidrIp, $ipProtocol, $sourcePortRange, $destCidrIp, $regionId);

        $regionId = $this->getRegionId($regionId);
        $portRange = $this->formatSecurityGroupRulePortRange($portRange);
        $sourcePortRange = $this->formatSecurityGroupRulePortRange($sourcePortRange);
        //dump($portRange);
        if ('all' == $ipProtocol) {
            $ipProtocol = null;
        }
        try {
            $request = (new CreateSecurityGroupRuleRequest())
                ->setBody(
                    (new CreateSecurityGroupRuleRequestBody())
                        ->setDryRun(false)
                        ->setSecurityGroupRule(
                            (new CreateSecurityGroupRuleOption())
                                ->setSecurityGroupId($securityGroupId)
                                ->setDirection($inOrOut)
                                ->setProtocol(in_array($ipProtocol, ['icmp', 'tcp', 'udp']) ? $ipProtocol : null)
                                ->setMultiport($portRange)
                                ->setRemoteIpPrefix($sourceCidrIp)
                                ->setAction($this->formatSecurityGroupRulePolicy($policy))
                                ->setPriority($priority)

                        )
                );
            $result = $this->clientVpcV3()->createSecurityGroupRule($request);

            return (bool)$result->valid();
        } catch (ServiceResponseException $exception) {
            //dump(func_get_args());
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    protected function securityGroupRevokeInOrOut(string $inOrOut, $securityGroupId, $portRange, $policy = 'accept', $sourceCidrIp = '0.0.0.0/0', $ipProtocol = 'tcp', $sourcePortRange = null, $destCidrIp = null, $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        $portRange = $this->formatSecurityGroupRulePortRange($portRange);
        $sourcePortRange = $this->formatSecurityGroupRulePortRange($sourcePortRange);
        if ('all' == $ipProtocol) {
            $ipProtocol = null;
        }
        try {
            $requestSearch = (new ListSecurityGroupRulesRequest())
                ->setSecurityGroupId((array)$securityGroupId)
                ->setDirection($inOrOut)
                ->setProtocol($ipProtocol)//->setAction($this->formatSecurityGroupRulePolicy($policy))
            ;

            $resultSearch = $this->clientVpcV3()->listSecurityGroupRules($requestSearch);
            $data = $resultSearch->getSecurityGroupRules();

            $rules = UtilArr::filter($data, function ($item) use ($portRange, $sourceCidrIp) {
                $bool = $item->getMultiport() == $portRange
                    && (
                        $item->getRemoteIpPrefix() == $sourceCidrIp
                        || (in_array($sourceCidrIp, ['0.0.0.0/0', '::/0']) && is_null($item->getRemoteIpPrefix()))
                    );
                return $bool;
            });

            if (empty($rules)) {
                return true;
            }

            $status = true;
            foreach ($rules as $rule){
                $request = (new DeleteSecurityGroupRuleRequest())
                    ->setSecurityGroupRuleId($rule->getId());;
                $result = $this->clientVpcV3()->deleteSecurityGroupRule($request);

                $status = $status && (bool)$result->valid();
            }
            return $status;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    /**
     * 添加入方向规则
     * @param $securityGroupId
     * @param string $ipProtocol tcp|udp|icmp|gre|all
     * @param string $portRange 目的端安全组开放的传输层协议相关的端口范围。取值范围: TCP/UDP协议：取值范围为1~65535。使用斜线（/）隔开起始端口和终止端口。例如：1/200;ICMP协议：-1/-1;GRE协议：-1/-1;IpProtocol取值为all：-1/-1
     * @param string $policy accept|drop
     * @param string $sourceCidrIp 源端IPv4 CIDR地址段。支持CIDR格式和IPv4格式的IP地址范围
     * @param string $sourcePortRange 源端安全组开放的传输层协议相关的端口范围。取值范围: TCP/UDP协议：取值范围为1~65535。使用斜线（/）隔开起始端口和终止端口。例如：1/200;ICMP协议：-1/-1;GRE协议：-1/-1;IpProtocol取值为all：-1/-1
     * @param string $destCidrIp 目的端IPv4 CIDR地址段。支持CIDR格式和IPv4格式的IP地址范围
     * @param int $priority 1~100
     * @param string $regionId
     * @return array
     */
    public function securityGroupAuthorize($securityGroupId, $portRange, $policy = 'accept', $sourceCidrIp = '0.0.0.0/0', $ipProtocol = 'tcp', $sourcePortRange = null, $destCidrIp = null, $priority = 1, $regionId = ''): bool
    {
        return $this->securityGroupAuthorizeInOrOut('ingress', $securityGroupId, $portRange, $policy, $sourceCidrIp, $ipProtocol, $sourcePortRange, $destCidrIp, $priority, $regionId);
    }

    public function securityGroupRevoke($securityGroupId, $portRange, $policy = 'accept', $sourceCidrIp = '0.0.0.0/0', $ipProtocol = 'tcp', $sourcePortRange = null, $destCidrIp = null, $regionId = ''): bool
    {
        return $this->securityGroupRevokeInOrOut('ingress', $securityGroupId, $portRange, $policy, $sourceCidrIp, $ipProtocol, $sourcePortRange, $destCidrIp, $regionId);

    }

    public function securityGroupEgressAuthorize($securityGroupId, $portRange, $policy = 'accept', $destCidrIp = '0.0.0.0/0', $ipProtocol = 'tcp', $sourcePortRange = null, $sourceCidrIp = null, $priority = 1, $regionId = ''): bool
    {
        return $this->securityGroupAuthorizeInOrOut('egress', $securityGroupId, $portRange, $policy, $destCidrIp, $ipProtocol, $sourcePortRange, $sourceCidrIp, $priority, $regionId);
    }

    public function securityGroupEgressRevoke($securityGroupId, $portRange, $policy = 'accept', $destCidrIp = '0.0.0.0/0', $ipProtocol = 'tcp', $sourcePortRange = null, $sourceCidrIp = null, $regionId = ''): bool
    {
        return $this->securityGroupRevokeInOrOut('egress', $securityGroupId, $portRange, $policy, $destCidrIp, $ipProtocol, $sourcePortRange, $sourceCidrIp, $regionId);
    }


    /**
     * @param null $instanceType 指定实例类型可以使用的镜像。ecs.g5.large
     * @param null $osType 镜像的操作系统类型 windows , linux
     * @param string $architecture 镜像的体系架构。取值范围： i386 x86_64
     * @param null $imageName 镜像名称  centos
     * @param int $pageNumber
     * @param int $pageSize
     * @param string $regionId
     * @return array
     */
    public function imagesDescribe(string $imageName = null, $imageId = null, $instanceType = null, $osType = null, $architecture = null, $regionId = '', $zoneId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);
        $models = new AssetModels();
        try {
            //$instanceType
            $request = (new ListImagesRequest());
            $request->setLimit(10000);
            $request->setIsregistered('true');
            //$request->setOsType('Windows');
            $request->setImagetype('gold');
            $request->setStatus('active');
            $request->setVirtualEnvType('FusionCompute');

            if ($imageId) {
                $request->setId($imageId);
            }
            if ($instanceType) {
                $request->setFlavorId($instanceType);
            }
            if ($osType) {
                $request->setOsType($osType);
            }
            if ($architecture) {
                $request->setOsBit($this->formatArchitecture($architecture));
            }

            $result_gold = $this->clientIms()->listImages($request);
            $request->setImagetype('private');
            $result_private = $this->clientIms()->listImages($request);


            $data_gold = (array)$result_gold->getImages();
            $data_private = (array)$result_private->getImages();

            //dump($data);
            foreach ($data_gold as $datum) {
                $id = $datum->getId();
                if (!$id) {
                    continue;
                }

                $model = new AssetModel();
                $model->type = 'image';
                $model->asset_id = $id;
                $model->name = $datum->getName();
                $model->os_type = $datum->getOsType();
                $model->os = $datum->getOsVersion();

                $models->push($model);
            }
            foreach ($data_private as $datum) {
                $id = $datum->getId();
                if (!$id) {
                    continue;
                }

                $model = new AssetModel();
                $model->type = 'image';
                $model->asset_id = $id;
                $model->name = $datum->getName();
                $model->os_type = $datum->getOsType();
                $model->os = $datum->getOsVersion();

                $models->push($model);
            }

            if ($imageName) {
                $models = $models->filter(function ($item) use ($imageName) {
                    return UtilStr::contains($item->name, $imageName, false);
                });
            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }


    public function imageDescribe(string $imageId, $regionId = '', $zoneId = ''): ?AssetModel
    {
        $models = $this->imagesDescribe(null, $imageId);
        if (!$models) {
            return null;
        }

        return $models->first(function ($item) use ($imageId) {
            return $item->asset_id == $imageId;
        });
        return null;
    }

    public function instanceTypesDescribe(array $instanceTypes = [],string $zoneId='',string $regionId = ''): AssetModels
    {
        $models = new AssetModels();
        try {
            $request = new ListFlavorsRequest();


            $result = $this->clientEcs()->listFlavors($request);
            $data = (array)$result->getFlavors();
            foreach ($data as $datum) {
                $model = $this->formatInstanceTypeToModel($datum);
                if (!$model) {
                    continue;
                }
                if (!empty($instanceTypes)) {
                    if (!in_array($model->asset_id, $instanceTypes)) {
                        continue;
                    }
                }
                $models->push($model);

            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function instanceTypeDescribe(string $instanceType,string $zoneId='',string $regionId = ''): ?AssetModel
    {
        $models = $this->instanceTypesDescribe([$instanceType]);
        if (!$models) {
            return null;
        }

        return $models->first(function ($item) use ($instanceType) {
            return $item->asset_id == $instanceType;
        });
        return null;
    }



    //实例

    /**
     * 创建实例
     * https://next.api.aliyun.com/api/Ecs/2014-05-26/CreateInstance?params={}
     * @param string $instanceType 实例规格
     * @param string $imageId 镜像文件ID
     * @param string $password 密码
     * @param string $instanceName 实例名 允许大小写英文字母、数字和短横线（-）
     * @param string $vSwitchId 虚拟交换机ID
     * @param int $internetMaxBandwidth 公网带宽峰值 0为不需要公网IP
     * @param null $securityGroupId 安全组ID
     * @param int $period 时长
     * @param string $periodUnit 时长单位 取值范围：Week和Month（默认
     * @param string $instanceChargeType 实例的付费方式。取值范围： PrePaid：包年包月,PostPaid 按量付费;选择该类付费方式时，您必须确认自己的账号支持余额支付/信用支付，否则将返回 InvalidPayMethod的错误提示。 PostPaid（默认）：按量付费。
     * @param string $internetChargeType 网络计费类型。取值范围：PayByBandwidth：按固定带宽计费。 PayByTraffic（默认）：按使用流量计费
     * @param array $request_data 其它数据
     * @param string $regionId
     * @param null $resourceGroupId 资源组ID
     * @return array
     */
    public function instanceCreate(string $instanceType, string $imageId, string $password, string $instanceName = null, string $vSwitchId = null, $internetMaxBandwidth = 0, $securityGroupId = null, $period = 1, $periodUnit = 'Month', $instanceChargeType = 'PostPaid', $internetChargeType = 'PayByTraffic', int $systemDiskSize = null,$zoneId = null, $regionId = '', $resourceGroupId = null): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);

        $internetMaxBandwidth = intval($internetMaxBandwidth);
        try {

            $image = $this->imageDescribe($imageId);
            if (!$image) {
                throw new \Exception('镜像不存在');
            }

            $instanceTypeModel = $this->instanceTypeDescribe($instanceType);
            if (!$instanceTypeModel) {
                throw new \Exception('实例规格不存在不存在');
            }

            if (!$instanceName) {
                $instanceName = UtilStr::randString($instanceName);
            }
            $systemDiskSize = intval($systemDiskSize);

            if (!$vSwitchId) {
                $this->setError("子网ID不能为空");
                return null;
            }
            if (!$vSwitch = $this->api->vpc($regionId)->vSwitchDescribe($vSwitchId)) {
                $this->setError("子网不存在:".$vSwitchId);
                return null;
            }
            $securityGroups = [];
            if ('PrePaid' == $instanceChargeType) {
                $body = new CreateServersRequestBody();
                $serverBody = new PrePaidServer();


                $nics = [
                    (new PrePaidServerNic())->setSubnetId($vSwitch->asset_id)
                ];
                $publicipServer = new PrePaidServerPublicip();
                $eipPublicip = new PrePaidServerEip();
                $bandwidthEip = new PrePaidServerEipBandwidth();

                if ($securityGroupId) {
                    foreach ((array)$securityGroupId as $sg_id) {
                        array_push($securityGroups, (new PrePaidServerSecurityGroup())->setId($sg_id));
                    }
                }

                $extendparamServer = new PrePaidServerExtendParam();
                $extendparamServer->setChargingMode("prePaid")
                    ->setRegionID($regionId)
                    ->setPeriodType($this->formatPeriodUnit($periodUnit))
                    ->setPeriodNum($period);
                $serverBody->setExtendparam($extendparamServer);

                $rootVolumeServer = new PrePaidServerRootVolume();
                $rootVolumeServer->setVolumetype("SAS");
                if ($systemDiskSize > 0) {
                    $rootVolumeServer->setSize($systemDiskSize);
                }
                $serverBody->setRootVolume($rootVolumeServer);

                $request = new CreateServersRequest();
            } else {
                $body = new CreatePostPaidServersRequestBody();
                $serverBody = new PostPaidServer();
                $nics = [
                    (new PostPaidServerNic())->setSubnetId($vSwitch->asset_id)
                ];
                $publicipServer = new PostPaidServerPublicip();
                $eipPublicip = new PostPaidServerEip();
                $bandwidthEip = new PostPaidServerEipBandwidth();
                if ($securityGroupId) {
                    foreach ((array)$securityGroupId as $sg_id) {
                        array_push($securityGroups, (new PostPaidServerSecurityGroup())->setId($sg_id));
                    }
                }

                $extendparamServer = new PostPaidServerExtendParam();
                $extendparamServer->setChargingMode(0)
                    ->setRegionID($regionId);
                $serverBody->setExtendparam($extendparamServer);

                $rootVolumeServer = new PostPaidServerRootVolume();
                $rootVolumeServer->setVolumetype("SAS");
                if ($systemDiskSize > 0) {
                    $rootVolumeServer->setSize($systemDiskSize);
                }
                $serverBody->setRootVolume($rootVolumeServer);

                $request = new CreatePostPaidServersRequest();
            }


            $serverBody->setFlavorRef($instanceType)
                ->setImageRef($imageId)
                ->setAdminPass($password)
                ->setName($instanceName)
                ->setVpcid($vSwitch->bind_asset_id)
                ->setNics(
                    $nics
                )
                ->setCount(1);
            if($zoneId){
                $serverBody->setAvailabilityZone(trim($zoneId));
            }

            if ($internetMaxBandwidth > 0) {
                $bandwidthEip->setSize($internetMaxBandwidth)
                    ->setSharetype("PER");
                if ('PayByTraffic' == $internetChargeType) {
                    $bandwidthEip->setChargemode('traffic');
                }else{
                    $bandwidthEip->setChargemode(null);
                }
                $eipPublicip->setIptype($this->getPublicIpType($regionId))
                    ->setBandwidth($bandwidthEip);
                $publicipServer->setEip($eipPublicip)->setDeleteOnTermination(true);
                $serverBody->setPublicip($publicipServer);
            }

            if (!empty($securityGroups)) {
                $serverBody->setSecurityGroups($securityGroups);
            }

            $body->setDryRun(false)
                ->setServer($serverBody);

            $request->setBody($body);

            if ('PrePaid' == $instanceChargeType) {
                $result = $this->clientEcs()->createServers($request);
            } else {
                $result = $this->clientEcs()->createPostPaidServers($request);
            }


            $id = UtilArr::first($result->getServerIds());
            if (!$id) {
                throw new \Exception('返回数据错误');
            }


            $model = new AssetModel();
            $model->type = 'ecs';
            $model->asset_id = $id;
            $model->name = (string)$instanceName;
            $model->instance_type = (string)$instanceType;
            $model->region_id = (string)$regionId;
            $model->os_password = (string)$password;

            $model->os_type = $image->os_type;
            $model->os = $image->name;
            $model->os_image = $image->asset_id;
            $model->cpu = $instanceTypeModel->cpu;
            $model->memory = $instanceTypeModel->memory;
            $model->bandwidth = (int)$internetMaxBandwidth;

            $model->os_account = UtilStr::contains($model->os_type, 'Windows', false) ? 'administrator' : 'root';

            $model->status = 'Pending';


            if (intval($internetMaxBandwidth) > 0) {
                $complete = false;
                UtilSupport::reTry(function () use (&$model, &$complete) {
                    if (!$temp = $this->instanceDescribe($model->asset_id)) {
                        return false;
                    }
                    if ('BUILD' != $temp->status) {
                        $model->merge($temp);
                        $complete = true;
                        return true;
                    }
                    return false;
                }, 10, 12);

                if ($complete) {
                    $publicIpAddrss = $this->publicIpAddressAllocate($id,$internetMaxBandwidth, $regionId);
                    if (!$publicIpAddrss) {
                        //$this->instancesDelete([$id],true,$regionId);
                        $this->setError('分配公网IP出错');
                    }
                    $model->public_ip = $publicIpAddrss;
                }
            }

            return $model;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }

    public function publicIpAddressAllocate(string $instanceId,int $bandwidth=100, $regionId = ''): string
    {
        $regionId = $this->getRegionId($regionId);
        $ipAddress = '';
        try {
            $model = $this->instanceDescribe($instanceId);
            if ($model) {
                $ipAddress = $model->public_ip;
            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $ipAddress;
    }


    public function instancesDescribe(array $instanceIds = [], $vSwitchId = null,$zoneId="", $regionId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);
        $models = new AssetModels();
        try {
            if (!empty($instanceIds)) {
                foreach ($instanceIds as $instanceId) {
                    if ($model = $this->instanceDescribe($instanceId)) {
                        $models->push($model);
                    }
                }
                return $models;
            }

            $request = (new ListServersDetailsRequest())->setLimit(1000);
            $result = $this->clientEcs()->ListServersDetails($request);


            $data = (array)$result->getServers();
            //dump($data);
            foreach ($data as $datum) {
                if ($model = $this->formatServerDetailToModel($datum)) {
                    if ('Deleted' != $model->status) {
                        $models->push($model);
                    }

                }
            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function instanceDescribe(string $instanceId,$zoneId="", $regionId = ''): ?AssetModel
    {
        try {
            $request = (new ShowServerRequest())
                ->setServerId($instanceId);
            $result = $this->clientEcs()->showServer($request);


            $datum = $result->getServer();

            $model = $this->formatServerDetailToModel($datum);

            if ('Deleted' == $model->status) {
                return null;
            }
            return $model;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }


    public function instancesStart(array $instanceId, $regionId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);
        $models = new AssetModels();
        try {
            $serverIds = [];
            foreach ($instanceId as $id) {
                array_push($serverIds,
                    (new ServerId())->setId($id)
                );
            }
            $request = (new BatchStartServersRequest())
                ->setBody(
                    (new BatchStartServersRequestBody())
                        ->setOsStart(
                            (new BatchStartServersOption())
                                ->setServers($serverIds)
                        )
                );
            $result = $this->clientEcs()->batchStartServers($request);

            if (!$result->valid()) {
                throw new \Exception('开启失败');
            }

            return $this->instancesDescribe($instanceId, null, $regionId);

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function instancesStatusDescribe(array $instanceId, $regionId = ''): AssetModels
    {
        return $this->instancesDescribe($instanceId, null, $regionId);
    }

    public function instanceStatusDescribe(string $instanceId, $regionId = ''): ?AssetModel
    {
        $models = $this->instancesStatusDescribe([$instanceId], $regionId);
        if (!$models) {
            return null;
        }

        return $models->first(function ($item) use ($instanceId) {
            return $item->asset_id == $instanceId;
        });
    }

    public function instancesStop(array $instanceId, $regionId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);
        $models = new AssetModels();
        try {
            $serverIds = [];
            foreach ($instanceId as $id) {
                array_push($serverIds,
                    (new ServerId())->setId($id)
                );
            }
            $request = (new BatchStopServersRequest())
                ->setBody(
                    (new BatchStopServersRequestBody())
                        ->setOsStop(
                            (new BatchStopServersOption())
                                ->setServers($serverIds)
                        )
                );
            $result = $this->clientEcs()->batchStopServers($request);

            if (!$result->valid()) {
                throw new \Exception('关机失败');
            }

            return $this->instancesDescribe($instanceId, null, $regionId);

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function instancesReboot(array $instanceId, $regionId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);

        $models = new AssetModels();
        try {
            $serverIds = [];
            foreach ($instanceId as $id) {
                array_push($serverIds,
                    (new ServerId())->setId($id)
                );
            }
            $request = (new BatchRebootServersRequest())
                ->setBody(
                    (new BatchRebootServersRequestBody())
                        ->setReboot(
                            (new BatchRebootSeversOption())
                                ->setServers($serverIds)
                                ->setType("HARD")
                        )
                );
            $result = $this->clientEcs()->batchRebootServers($request);

            if (!$result->valid()) {
                throw new \Exception('重启失败');
            }

            return $this->instancesDescribe($instanceId, null, $regionId);

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function instancesDelete(array $instanceId, $force = false, $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        if (empty($instanceId)) {
            return true;
        }

        try {

            $body = new DeleteServersRequestBody();
            $bodyServers = [];
            foreach ($instanceId as $id) {
                array_push($bodyServers,
                    (new ServerId())
                        ->setId($id)
                );
            }
            $body->setServers($bodyServers)
                ->setDeletePublicip(false)
                ->setDeleteVolume(false);

            $request = (new DeleteServersRequest())
                ->setBody($body);
            $result = $this->clientEcs()->deleteServers($request);

            return (bool)$result->valid();
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function instanceAttributeModify(string $instanceId, string $password = '',string $name='',string $hostName='', $userData = null, $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        $name = trim($name);
        $hostName = trim($hostName);
        try {
            $status = true;


            if ($name || $hostName) {
                $request = new UpdateServerRequest();
                $request->setServerId($instanceId);
                $body = new UpdateServerRequestBody();
                $serverbody = new UpdateServerOption();
                if($name){
                    $serverbody->setName($name);
                }
                if($hostName){
                    $serverbody->setHostname($hostName);
                }
                $body->setServer($serverbody);
                $request->setBody($body);

                $result = $this->clientEcs()->updateServer($request);
                $status = (bool)$result->valid();
            }

            if ($status && $password) {
                $request = new ResetServerPasswordRequest();
                $request->setServerId($instanceId)
                    ->setBody(
                        (new ResetServerPasswordRequestBody())
                            ->setResetPassword(
                                (new ResetServerPasswordOption())
                                    ->setNewPassword($password)
                                    ->setIsCheckPassword(true)
                            )
                    );
                $result = $this->clientEcs()->resetServerPassword($request);
                $status = (bool)$result->valid();
            }

            return $status;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }


    public function instanceVncPasswdModify(string $instanceId, string $vncPassword, $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);

        return $this->instanceDescribe($instanceId, $regionId);
    }

    public function instanceVncUrlDescribe(string $instanceId, string $vncPassword = '', $regionId = ''): string
    {
        $regionId = $this->getRegionId($regionId);

        try {
            $instance = $this->instanceDescribe($instanceId);
            if (!$instance) {
                throw new \Exception('实例不存在');
            }
            $oSType = $instance->os_type;

            $request = new ShowServerRemoteConsoleRequest();

            $request->setServerId($instanceId);
            $request->setBody(
                (new ShowServerRemoteConsoleRequestBody())
                    ->setRemoteConsole(
                        (new GetServerRemoteConsoleOption())
                            ->setType('novnc')
                            ->setProtocol('vnc')
                    )
            );

            $result = $this->clientEcs()->showServerRemoteConsole($request);

            $vncUrl = $result->getRemoteConsole();

            if (!$vncUrl) {
                throw new \Exception('获取vnc地址失败');
            }

            return $vncUrl->getUrl();
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return '';
    }

    public function instanceChangeVpc(string $instanceId, string $vSwitchId ,array $securityGroupId=[], $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        if (empty($instanceId)) {
            return true;
        }

        try {
            throw new \Exception('不支持通过接口切换vpc,请手动切换');
            //todo noapi
            $result = $this->clientEcs()->withEndpoint('https://console.huaweicloud.com')->doHttpRequest(
                'POST'
                , '/ecm/rest/ecs/v1/{project_id}/cloudservers/{server_id}/changevpc'
                , [
                    'server_id' => $instanceId
                ]
                , []
                , [
                    'Content-Type' => 'application/json'
                ]
                , null
                , null
                , [
                    'vpc_id'=>'9fcbd438-89af-4baf-8cba-a5e618d72b1d',
                    'nic' => [
                        'subnet_id' => '3773ff10-2e2b-4f0b-a385-842646bf4302',
                        'security_groups' => [
                            [
                                'id' => '2de26c63-5fee-476d-92c8-e8cbc5197240',
                            ]
                        ],
                        'ip_address' => '',
                    ]
                ]
                , ChangeVpcResponse::class
            );


            if (!$result->valid()) {
                throw new \Exception('绑定失败');
            }

            return (bool)$result->valid();
        } catch (ServiceResponseException $exception) {
            dump($exception);
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            dump($exception);
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function systemDiskReplace(string $instanceId, string $imageId, string $password, $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);

        try {

            $image = $this->imageDescribe($imageId);
            if (!$image) {
                throw new \Exception('镜像不存在');
            }

            $model = $this->instanceDescribe($instanceId);
            if (!$model) {
                throw new \Exception('实例不存在');
            }

            if ('Stopped' != $model->status) {
                throw new \Exception('请先停止实例');
            }


            if ($model->os_image == $image->asset_id) {
                $request = new ReinstallServerWithoutCloudInitRequest();
                $request->setServerId($model->asset_id)
                    ->setBody(
                        (new ReinstallServerWithoutCloudInitRequestBody())
                            ->setOsReinstall(
                                (new ReinstallServerWithoutCloudInitOption())
                                    ->setAdminpass($password)
                                    ->setMode('withStopServer')
                            )
                    );
                $result = $this->clientEcs()->reinstallServerWithoutCloudInit($request);
            } else {
                $request = new ChangeServerOsWithoutCloudInitRequest();
                $request->setServerId($model->asset_id)
                    ->setBody(
                        (new ChangeServerOsWithoutCloudInitRequestBody())
                            ->setOsChange(
                                (new ChangeServerOsWithoutCloudInitOption())
                                    ->setImageid($image->asset_id)
                                    ->setAdminpass($password)
                                    ->setMode('withStopServer')
                            )
                    );
                $result = $this->clientEcs()->changeServerOsWithoutCloudInit($request);
            }


            if ((bool)$result->valid()) {
                $model->os_password = $password;

                $model->os_type = $image->os_type;
                $model->os_image = $image->asset_id;
                $model->os = $image->name;
                $model->status = 'Pending';

                $model->os_account = UtilStr::contains($model->os_type, 'Windows', false) ? 'administrator' : 'root';
                return $model;
            }
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }

    public function systemDiskSnapshotCreate(string $instanceId, string $name='', $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        $instanceId = trim($instanceId);
        if (!$instanceId) {
            return false;
        }



        try {
            $request = new ListServerBlockDevicesRequest();
            $request->setServerId($instanceId);
            $response = $this->clientEcs()->ListServerBlockDevices($request);
            $disks = $response->getVolumeAttachments();
            $sysDisk = UtilArr::first($disks,function (ServerBlockDevice $disk){
                return 0 === $disk->getBootIndex();
            });
            if(!$sysDisk){
                throw new \Exception("没找到系统磁盘");
            }
            return $this->diskSnapshotCreate($sysDisk->getVolumeId(),$name,$regionId);

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }

        return false;
    }

    //


    public function networkInterfaceCreate(string $vSwitchId, string $networkInterfaceName = null, array $securityGroupIds = [], $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);

        try {
            $portBody = (new CreatePortOption())
                ->setNetworkId($vSwitchId);
            if ($networkInterfaceName) {
                $portBody->setName($networkInterfaceName);
            }
            if (!empty($securityGroupIds)) {
                $portBody->setSecurityGroups($securityGroupIds);
            }
            $request = (new CreatePortRequest())
                ->setBody(
                    (new CreatePortRequestBody())
                        ->setPort($portBody)
                );
            $result = $this->clientVpcV2()->createPort($request);

            $datum = $result->getPort();

            return $this->formatEniToModel($datum);
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }


    public function networkInterfacesDescribe(array $networkInterfaceId = [], $instanceId = null, $regionId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);

        $models = new AssetModels();

        try {
            if (!empty($networkInterfaceId)) {
                foreach ($networkInterfaceId as $id) {
                    if ($model = $this->networkInterfaceDescribe($id)) {
                        $models->push($model);
                    }

                }
                //　todo no api
                return $models;
            }

            $request = (new ListPortsRequest())->setLimit(1000);
            if ($instanceId) {
                $request->setDeviceId($instanceId);
            }
            $result = $this->clientVpcV2()->listPorts($request);

            $data = (array)$result->getPorts();

            //dump($data);
            foreach ($data as $datum) {
                if ($model = $this->formatEniToModel($datum)) {
                    $models->push($model);
                }
            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function networkInterfaceDescribe(string $networkInterfaceId, $regionId = ''): ?AssetModel
    {
        try {
            $request = (new ShowPortRequest())->setPortId($networkInterfaceId);
            $result = $this->clientVpcV2()->showPort($request);
            $datum = $result->getPort();
            return $this->formatEniToModel($datum);

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }

    public function networkInterfaceDelete(string $networkInterfaceId, $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        $networkInterfaceId = trim($networkInterfaceId);
        if (!$networkInterfaceId) {
            return false;
        }
        if (!$this->networkInterfaceDescribe($networkInterfaceId)) {
            return true;
        }
        try {
            $request = (new DeletePortRequest())->setPortId($networkInterfaceId);
            $result = $this->clientVpcV2()->deletePort($request);

            return (bool)$result->valid();
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function networkInterfaceAttach(string $networkInterfaceId, string $instanceId, $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);

        try {
            $networkInterfaceModel = $this->networkInterfaceDescribe($networkInterfaceId, $regionId);
            if (!$networkInterfaceModel) {
                throw new \Exception('弹性网卡不存在');
            }
            $instanceModel = $this->instanceDescribe($instanceId, $regionId);
            if (!$instanceModel) {
                throw new \Exception('实例不存在');
            }

            //todo noapi
            $result = $this->clientEcs()->doHttpRequest(
                'POST'
                , '/v2.1/{project_id}/servers/{server_id}/os-interface'
                , [
                    'server_id' => $instanceId
                ]
                , []
                , [
                    'Content-Type' => 'application/json'
                ]
                , null
                , null
                , [
                    'interfaceAttachment' => [
                        'port_id' => $networkInterfaceId
                    ]
                ]
                , ShowServerInterfaceResponse::class
            );

            //$networkInterface = $result->getInterfaceAttachment();

            if (!$result->valid()) {
                throw new \Exception('绑定失败');
            }


            /*$request = (new BatchAddServerNicsRequest())
                ->setServerId($instanceId)
                ->setBody(
                    (new BatchAddServerNicsRequestBody())
                        ->setNics([
                            (new BatchDeleteServerNicOption())
                                ->setId($networkInterfaceId)
                        ])
                )
            ;
            $result = $this->clientEcs()->batchAddServerNics($request);
            if(!$result->valid()){
                return null;
            }*/

            $instanceModel->second_ip = $networkInterfaceModel->primary_ip;
            return $instanceModel;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }

    public function networkInterfaceDetach(string $networkInterfaceId, string $instanceId, $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);
        try {
            $instanceModel = $this->instanceDescribe($instanceId, $regionId);
            if (!$instanceModel) {
                throw new \Exception('实例不存在');
            }

            //todo noapi
            $result = $this->clientEcs()->doHttpRequest(
                'DELETE'
                , '/v2.1/{project_id}/servers/{server_id}/os-interface/{port_id}'
                , [
                    'server_id' => $instanceId,
                    'port_id' => $networkInterfaceId,
                ]
                , []
                , [
                    'Content-Type' => 'application/json'
                ]
                , null
                , null
                , []
                , DetachServerInterfaceResponse::class
                , null
                , null
                , false
            );

            //dd($result);

            if (!$result->valid()) {
                throw new \Exception('解绑失败');
            }


            /*$request = (new BatchDeleteServerNicsRequest())
            ->setServerId($instanceId)
                ->setBody(
                    (new BatchDeleteServerNicsRequestBody())
                    ->setNics([
                        (new BatchDeleteServerNicOption())
                            ->setId($networkInterfaceId)
                    ])
                )
            ;
            $result = $this->clientEcs()->batchDeleteServerNics($request);
            if(!$result->valid()){
                return null;
            }*/


            $instanceModel->second_ip = '';
            return $instanceModel;
        } catch (ServiceResponseException $exception) {
            dd($exception);
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            dd($exception);
            $this->setError($exception->getMessage());
        }
        return null;
    }


    public function commandCreate(string $commandContent, string $name, string $type = 'RunShellScript', $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);
        //todo noapi
        $model = new AssetModel();
        $model->type = 'cmd';
        $model->asset_id = uniqid('virtual-');

        $model->name = (string)$name;
        $model->os_type = (string)$type;
        $model->cmd_content = (string)$commandContent;
        $model->region_id = (string)$regionId;


        $model->status = 'Available';
        return $model;

        //


        try {
            $request = new CreateCommandRequest([
                'regionId' => $regionId,
                'name' => $name,
                'type' => $type,
                'commandContent' => base64_encode($commandContent),
            ]);
            $result = $this->client->createCommand($request);

            $data = $result->body->toMap();

            $id = UtilArr::get($data, 'CommandId');
            if (!$id) {
                throw new \Exception('返回数据错误');
            }


            $model = new AssetModel();
            $model->type = 'cmd';
            $model->asset_id = $id;

            $model->name = (string)$name;
            $model->os_type = (string)$type;
            $model->cmd_content = (string)$commandContent;
            $model->region_id = (string)$regionId;


            $model->status = '0';
            return $model;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }

    public function commandsDescribe(string $commandId = null, string $name = null, string $type = null, $regionId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);

        $models = new AssetModels();

        if ($commandId) {
            $model = new AssetModel();
            $model->type = 'cmd';
            $model->asset_id = $commandId;

            $model->status = 'Available';
        }
        //todo noapi
        return $models;
        try {
            $request = new DescribeCommandsRequest([
                'regionId' => $regionId,
                'commandId' => !empty($commandId) ? $commandId : null,
                'name' => !empty($name) ? $name : null,
                'type' => !empty($type) ? $type : null,
            ]);
            $result = $this->client->describeCommands($request);

            $data = (array)UtilArr::get($result->body->toMap(), 'Commands.Command');

            foreach ($data as $datum) {
                $id = UtilArr::get($datum, 'CommandId');
                if (!$id) {
                    continue;
                }

                $model = new AssetModel();
                $model->type = 'cmd';
                $model->asset_id = $id;
                $model->name = UtilArr::get($datum, 'Name');
                $model->os_type = UtilArr::get($datum, 'Type');
                $model->cmd_content = @base64_decode(UtilArr::get($datum, 'CommandContent'));
                $model->region_id = (string)$regionId;


                $model->status = 'Available';
                //$model->status = UtilArr::get($datum,'InvokeTimes');

                $models->push($model);
            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function commandDescribe(string $commandId, $regionId = ''): ?AssetModel
    {
        $models = $this->commandsDescribe($commandId, null, null, $regionId);
        if (!$models) {
            return null;
        }

        return $models->first(function ($item) use ($commandId) {
            return $item->asset_id == $commandId;
        });
    }

    public function commandDelete(string $commandId, $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        //todo noapi
        return true;
        //
        $commandId = trim($commandId);
        if (!$commandId) {
            return false;
        }

        if (!$this->commandDescribe($commandId)) {
            return true;
        }

        try {
            $request = new DeleteCommandRequest([
                'regionId' => $regionId,
                'commandId' => $commandId,
            ]);
            $result = $this->client->deleteCommand($request);

            return (bool)$result->body->toMap();
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function commandInvoke(string $commandId, array $instanceId, string $frequency = null, $regionId = ''): ?string
    {
        $regionId = $this->getRegionId($regionId);
        //todo noapi
        return uniqid('virtual-');
        //
        try {
            $request = new InvokeCommandRequest([
                'regionId' => $regionId,
                'commandId' => $commandId,
                'instanceId' => $instanceId,
                'timed' => $frequency ? 'true' : 'false',
                'frequency' => $frequency,
            ]);
            $result = $this->client->invokeCommand($request);

            $invokeId = UtilArr::get($result->body->toMap(), 'InvokeId');
            if (!$invokeId) {
                throw new \Exception('返回数据错误');
            }
            return $invokeId;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return "";
    }

    public function commandRun(string $commandContent, array $instanceIds, string $type = 'RunShellScript', string $contentEncoding = 'PlainText', $regionId = ''): ?string
    {
        $regionId = $this->getRegionId($regionId);
        //todo noapi
        return uniqid('virtual-');
        //
        try {
            $request = new RunCommandRequest([
                'regionId' => $regionId,
                "instanceId" => $instanceIds,
                "commandContent" => $commandContent,
                "type" => $type,
                "contentEncoding" => $contentEncoding,
            ]);
            $result = $this->client->runCommand($request);

            $data = $result->body->toMap();

            $invokeId = UtilArr::get($data, 'InvokeId');
            if (!$invokeId) {
                throw new \Exception('返回数据错误');
            }
            return $invokeId;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return "";
    }

    public function commandInvocationsDescribe(string $invokeId = '', string $commandId = '', string $instanceId = '', $regionId = ''): ?array
    {
        $regionId = $this->getRegionId($regionId);
        //todo noapi
        return [];
        try {
            $request = new DescribeInvocationsRequest([
                'regionId' => $regionId,
                "invokeId" => $invokeId,
                "commandId" => $commandId,
                "instanceId" => $instanceId,
            ]);
            $result = $this->client->describeInvocations($request);

            $data = $result->body->toMap();

            $invocations = (array)UtilArr::get($data, 'Invocations.Invocation');
            $invokeInstances = (array)UtilArr::get($invocations, 'InvokeInstances.InvokeInstance');
            unset($invocations['InvokeInstances']);
            foreach ($invokeInstances as $invokeInstance) {
                $invokeInstanceId = UtilArr::get($invokeInstance, 'InstanceId');
                if (!$invokeInstanceId) {
                    continue;
                }

                $invocations['InvokeInstances'][$invokeInstanceId] = $invokeInstance;
            }
            unset($data['Invocations']);
            foreach ($invocations as $invocation) {
                $invocationId = UtilArr::get($invocation, 'InvokeId');
                if (!$invocationId) {
                    continue;
                }

                $data['Invocations'][$invocationId] = $invocation;
            }
            return $data;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return [];
    }

    public function diskCreate(int $size, string $diskCategory = null, string $diskName = null, string $instanceId = null, string $zoneId = null, $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);
        //todo api未完善，返回ID为空
        $this->setError("华为云接口有问题，斩不支持创建硬盘");
        return null;

        if (!$diskCategory) {
            $diskCategory = 'cloud';
        }

        try {
            $request = new CreateVolumeRequest();
            $request->setBody(
                (new CreateVolumeRequestBody())
                    ->setServerId($instanceId)
                    ->setVolume(
                        (new CreateVolumeOption())
                            ->setAvailabilityZone($zoneId)
                            ->setSize($size)
                            ->setVolumeType($this->formatDiskCategory($diskCategory, false))
                            ->setName($diskName)
                    )
            );
            $result = $this->clientEvs()->createVolume($request);

            $data = $result->getVolumeIds();


            $id = UtilArr::first($data);
            if (!$id) {
                throw new \Exception('返回数据错误');
            }


            $model = new AssetModel();
            $model->type = 'disk';
            $model->asset_id = $id;

            $model->name = (string)$diskName;
            $model->region_id = (string)$regionId;
            $model->memory = $size;
            $model->bind_asset_id = $instanceId;


            $model->status = 'Pending';
            return $model;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }


    public function disksDescribe(array $diskIds = [], $instanceId = null, $regionId = ''): AssetModels
    {
        $regionId = $this->getRegionId($regionId);

        $models = new AssetModels();
        try {

            $request = new ListVolumesRequest();
            if (!empty($diskIds)) {
                $request->setIds(json_encode($diskIds));
            }
            if ($instanceId) {
                $request->setServerId($instanceId);
            }
            $result = $this->clientEvs()->listVolumes($request);

            $data = (array)$result->getVolumes();

            //dump($data);
            foreach ($data as $datum) {
                $id = $datum->getId();
                if (!$id) {
                    continue;
                }

                $model = new AssetModel();
                $model->type = 'disk';
                $model->asset_id = $id;

                $model->name = $datum->getName();
                $model->region_id = (string)$regionId;
                $model->memory = $datum->getSize();

                $attachment = UtilArr::first((array)$datum->getAttachments());
                if ($attachment) {
                    $model->bind_asset_id = $attachment->getServerId();
                    $model->os = $attachment->getDevice();
                }


                $model->status = $datum->getStatus();
                $model->status = $this->formatStatus('disk', $model->status);

                $models->push($model);
            }

        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return $models;
    }

    public function diskDescribe(string $diskId, $regionId = ''): ?AssetModel
    {
        $models = $this->disksDescribe([$diskId], null, $regionId);
        if (!$models) {
            return null;
        }

        return $models->first(function ($item) use ($diskId) {
            return $item->asset_id == $diskId;
        });
    }
    public function diskUpdate(string $diskId,$name='', $regionId = ''): ?AssetModel
    {
        $model = $this->diskDescribe($diskId,  $regionId);
        if (!$model) {
            throw new \Exception('云盘不存在');
            return null;
        }
        $name = trim($name);
        if(!$name){
            return $model;
        }

        try {
            $request = new UpdateVolumeRequest();
            $request->setVolumeId($diskId);

            $body = new UpdateVolumeRequestBody();
            $volumebody = new UpdateVolumeOption();
            $volumebody->setName($name);
            $body->setVolume($volumebody);
            $request->setBody($body);
            $result = $this->clientEvs()->updateVolume($request);


            if(!(bool)$result->valid()){
                throw new \Exception('返回数据错误');
            }


            if($name ){
                $model->name = $name;
            }
            return $model;
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }


    public function diskAttach(string $diskId, string $instanceId, bool $bootable = false, bool $deleteWithInstance = false, $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);

        try {
            $request = new AttachServerVolumeRequest();
            $request->setServerId($instanceId)
                ->setBody(
                    (new AttachServerVolumeRequestBody())
                        ->setVolumeAttachment(
                            (new AttachServerVolumeOption())
                                ->setVolumeId($diskId)
                        )
                );
            $result = $this->clientEcs()->attachServerVolume($request);


            return $this->diskDescribe($diskId);
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }

    public function diskDetach(string $diskId, string $instanceId, $regionId = ''): ?AssetModel
    {
        $regionId = $this->getRegionId($regionId);

        try {
            $request = new DetachServerVolumeRequest();
            $request->setVolumeId($diskId)
                ->setServerId($instanceId)
                ->setDeleteFlag(1);
            $result = $this->clientEcs()->detachServerVolume($request);


            return $this->diskDescribe($diskId);
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return null;
    }


    public function diskDelete(string $diskId, $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        $diskId = trim($diskId);
        if (!$diskId) {
            return false;
        }
        if (!$this->diskDescribe($diskId)) {
            return true;
        }
        try {
            $request = (new DeleteVolumeRequest())->setVolumeId($diskId);
            $result = $this->clientEvs()->deleteVolume($request);

            return (bool)$result->valid();
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }

    public function diskSnapshotCreate(string $diskId, string $name='', $regionId = ''): bool
    {
        $regionId = $this->getRegionId($regionId);
        $diskId = trim($diskId);
        if (!$diskId) {
            return false;
        }
        if (!$this->diskDescribe($diskId)) {
            return false;
        }
        $name = trim($name);

        if ("" == $name) {
            $name = date("Ymd-His", time());
        }

        try {
            $request = new CreateSnapshotRequest();

            $body = new CreateSnapshotRequestBody();
            $snapshotbody = new CreateSnapshotOption();
            $snapshotbody->setVolumeId($diskId)
                ->setForce(true)
                ->setName($name);
            $body->setSnapshot($snapshotbody);
            $request->setBody($body);
            $result = $this->clientEvs()->createSnapshot($request);

            return (bool)$result->valid();
        } catch (ServiceResponseException $exception) {
            $this->setError($exception->getErrorMsg());
        } catch (\Exception $exception) {
            $this->setError($exception->getMessage());
        }
        return false;
    }


    protected function formatSecurityGroupRulePolicy($policy, $flip = false)
    {
        $arr = [
            'accept' => 'allow',
            'drop' => 'deny',
        ];
        if ($flip) {
            $arr = array_flip($arr);
        }

        return UtilArr::get($arr, $policy, '');
    }

    protected function formatArchitecture($architecture, $flip = false)
    {
        $arr = [
            'i386' => '32',
            'x86_64' => '64',
        ];
        if ($flip) {
            $arr = array_flip($arr);
        }

        return UtilArr::get($arr, $architecture, '');
    }

    protected function formatSecurityGroupRulePortRange($portRange)
    {
        $ports = explode('/', $portRange);
        if (count($ports) >= 2 && $ports[0] == $ports[1]) {
            $portRange = $ports[0];
        }
        if ('-1' == $portRange) {
            return null;
        }
        $portRange = UtilStr::replace($portRange, ['\/' => '-']);


        return $portRange;
    }


    /**
     * @param $datum ServerDetail
     * @return AssetModel|null
     */
    protected function formatServerDetailToModel($datum)
    {

        $id = $datum->getId();
        if (!$id) {
            //throw new \Exception("返回ID为空");
            return null;
        }


        $metadata = $datum->getMetadata();

        $flavor = $datum->getFlavor();
        $ipAddresses = UtilArr::collapse($datum->getAddresses());

        $primaryAddress = UtilArr::first($ipAddresses, function ($item) {
            return 'fixed' == $item->getOsExtIpStype();
        });
        $secondAddress = UtilArr::first($ipAddresses, function ($item) {
            return 'fixed' == $item->getOsExtIpStype();
        }, null, 1);
        $publicAddress = UtilArr::first($ipAddresses, function ($item) {
            return 'floating' == $item->getOsExtIpStype();
        });


        $model = new AssetModel();
        $model->type = 'ecs';
        $model->asset_id = $id;
        $model->name = $datum->getName();

        $model->os_type = UtilArr::get($metadata, 'os_type');
        $model->instance_type = $flavor ? $flavor->getId() : '';
        $model->region_id = UtilStr::substr($datum->getOsExtAZavailabilityZone(), 0, -1);
        $model->cpu = $flavor ? intval($flavor->getVcpus()) : 0;
        $model->memory = $flavor ? $flavor->getRam() : 0;
        $model->bandwidth = 0;
        $model->primary_ip = $primaryAddress ? $primaryAddress->getAddr() : '';
        $model->second_ip = $secondAddress ? $secondAddress->getAddr() : '';
        $model->public_ip = '';
        $model->bind_asset_id = '';
        if ($publicAddress) {
            $model->public_ip = $publicAddress->getAddr();

            $eipModel = $this->api->vpc($this->getRegionId())->eipAddressDescribe('', $publicAddress->getAddr());
            if ($eipModel) {
                $model->bind_asset_id = $eipModel->asset_id;
            }

        }


        $model->os = UtilArr::get($metadata, 'image_name');
        $model->os_image = array_key_exists('metering.image_id', $metadata) ? $metadata['metering.image_id'] : '';
        $model->os_account = UtilStr::contains($model->os_type, 'Windows', false) ? 'administrator' : 'root';
        $model->status = $datum->getStatus();
        $model->status = $this->formatStatus('ecs', $model->status);
        $model->pay_change_type = $this->formatPayChangeType(UtilArr::get($metadata, 'charging_mode'));
        return $model;
    }

    protected function formatInstanceTypeToModel($datum)
    {
        $id = $datum->getId();
        if (!$id) {
            //throw new \Exception("返回数据ID为空");
            return null;
        }
        $model = new AssetModel();
        $model->type = 'ecs_type';
        $model->asset_id = $id;
        $model->cpu = $datum->getVcpus();
        $model->memory = $datum->getRam();
        $model->name = $datum->getName() . '-' . $model->cpu . '核' . ($model->memory / 1024) . 'G';
        return $model;
    }

    protected function formatEniToModel($datum)
    {
        //dump($datum);
        $id = $datum->getId();
        if (!$id) {
            return null;
        }

        $model = new AssetModel();
        $model->type = 'eni';
        $model->asset_id = $id;

        $model->name = $datum->getName();;
        $model->region_id = $this->getRegionId();
        $ips = (array)$datum->getFixedIps();
        $model->primary_ip = '';
        $model->second_ip = '';
        if (!empty($ips)) {
            $priIp = UtilArr::first($ips, null, 0);
            if ($priIp) {
                $model->primary_ip = $priIp->getIpAddress();
            }
            $secondIp = UtilArr::first($ips, null, 1);
            if ($secondIp) {
                $model->primary_ip = $secondIp->getIpAddress();
            }
        }
        $bind_asset_id = $datum->getDeviceId();
        $model->bind_asset_id = $bind_asset_id ?? "";

        $model->status = $this->formatStatus('eni', $datum->getStatus());
        return $model;
    }

    protected function formatDiskCategory($diskCategory, $flip = false)
    {
        $arr = [
            'cloud' => 'SAS',
            'cloud_efficiency' => 'SAS',
            'cloud_ssd' => 'GPSSD',
            'cloud_essd' => 'SSD',
        ];
        if ($flip) {
            $arr = array_flip($arr);
        }

        return UtilArr::get($arr, $diskCategory, '');
    }

    protected function formatPeriodUnit($diskCategory, $flip = false)
    {
        $arr = [
            'year' => 'month',
            'Week' => 'month',
            'Month' => 'month',
        ];
        if ($flip) {
            $arr = array_flip($arr);
        }

        return UtilArr::get($arr, $diskCategory, '');
    }

}

